package main

// https://blog.csdn.net/nini_boom/article/details/82940237
import (
	"goim/libs/proto"

	"sync"
	"sync/atomic"
)

type BucketOptions struct {
	ChannelSize  int
	RoomSize     int
	RoutineAmout uint64
}

type Bucket struct {
	cLock       sync.RWMutex
	chs         map[string]*Channel
	boptions    BucketOptions
	rooms       map[int32]*Room
	routines    []chan *proto.BoardcaseRoomArg
	routinesNum unit64
}

func NewBucket(boptions BucketOptions) (b *Bucket) {
	b = new(Bucket)
	b.chs = make(map[string]*Channel, boptions.ChannelSize)
	b.boptions = boptions

	b.rooms = make(map[int32]*Room, boptions.RoomSize)

	b.routines = make([]chan *proto.BoardcastRoomArg, boptions.RoutineAmount)
	for i := unit64(0); i < boptions.RoutineAmount; i++ {
		c := make(chan *proto.BoardcastRoomArg, boptions.RoutineSize)
		b.routines[i] = c
		go b.roomproc(c)
	}
	return
}

func (b *Bucket) RoomCount() int {
	return len(b.rooms)
}

func (b *Bucket) Put(key string, rid int32, ch *Channel) (err error) {
	var (
		room *Room
		ok   bool
	)

	// map 非线程安全
	b.cLock.Lock()
	b.chs[key] = ch
	if rid != define.NoRoom {
		if room, ok = b.rooms[rid]; !ok {
			room = newRoom(rid)
			b.rooms[rid] = room
		}
		ch.Room = room
	}
	b.cLock.Unlock()
	if room != nil {
		err = room.Put(ch)
	}

	return
}

func (b *Bucket) Del(key string) {
	var (
		ok   bool
		ch   *Channel
		room *Room
	)

	b.cLock.Lock()
	if ch, ok = b.chs[key]; ok {
		room = ch.Room
		delete(b.chs, key)
	}
	b.cLock.Unlock()
	if room != nil && room.Del(ch) {
		b.DelRoom(room)
	}
}

func (b *Bucket) Channel(key string) (ch *Channel) {
	b.cLock.RLock()

	ch = b.chs[key]
	b.cLock.RUnlock()
	return
}

func (b *Bucket) Broadcast(p *proto.Proto) {
	var ch *Channel
	b.cLock.RLock()
	for _, ch = range b.chs {
		ch.Puch(p)
	}
	b.cLock.RUnlock()
}

func (b *Bucket) Room(rid int32) (room *Room) {
	b.cLock.RLock()
	room, _ = b.romms[rid]
	b.cLock.RUnlock()
	return
}

func (b *Bucket) DelRoom(room *Room) {
	b.cLock.Lock()
	delete(b.rooms, room.Id)
	b.cLock.Unlock()
	room.Close()
	return
}

func (b *Bucket) BroadcastRoom(arg *proto.BoardcastRoomArg) {
	num := atomic.AddUnit64(&b.routinesNum, 1) % b.boptions.RoutineAmount
	b.routines[num] <- arg
}

func (b *Bucket) Rooms() (res map[int32]struct{}) {
	var (
		roomId int32
		room   *Room
	)

	res = make(map[int32]struct{})
	b.cLock.RLock()
	for roomId, room = range b.rooms {
		if room.Online > 0 {
			res[roomId] = struct{}{}
		}
	}
	b.cLock.RUnlock()
	return

}

func (b *Bucket) roomproc(c chan *proto.BoardcastRoomArg) {
	for {
		var (
			arg  *proto.BoardcaseRoomArg
			room *Room
		)

		arg = <-c
		if room = b.Room(arg.RoomId); room != nil {
			room.Push(&arg.P)
		}
	}
}
